/* Copyright (c) 1993-1996 by NeXT Software, Inc. as an unpublished work.
 * All rights reserved.
 *
 * QVisionSetMode.m -- Mode support for the QVision.
 *
 * Author:  Derek B Clegg	21 May 1993
 *	Based on work by Joe Pasqua.
 * 16 Aug 1994 James C. Lee
 *	Add 3.3 bus support & 8-bit color support.
 * 09 Oct 1995 Rakesh Dubey
 *	Added support for QV64P10.
 */

#import <string.h>
#import <driverkit/generalFuncs.h>
#import <driverkit/i386/ioPorts.h>
#import <driverkit/i386/directDevice.h>
#import <driverkit/i386/IOPCIDeviceDescription.h>
#import <driverkit/i386/IOPCIDirectDevice.h>
#import <string.h>
#import <stdio.h>

#import "QVision.h"

/* The `SetMode' category of `QVision'. */

@implementation QVision (SetMode)

- (ConfigBusType) determineConfigBusType
{
    const char *busTypeName;
    IOConfigTable *configTable;

    configTable = [[self deviceDescription] configTable];
    busTypeName = [configTable valueForStringKey:"Bus Type"];
    if (strcmp(busTypeName, "PCI") == 0) {
	busType = BusPCI;
    } else if (strcmp(busTypeName, "EISA") == 0) {
	busType = BusEISA;
    } else {
	/* default to ISA or VL */
	busType = BusISAorVL;
    }
    return busType;
}

/* assume dac type is determined already. This method is called by
 * determineConfiguration only */
- (QVAdapterType) determineVLCardType
{
	QVAdapterType adapterType;

	adapterType = UnknownAdapter;
	switch (dac) {
	  case Bt484:
	    adapterType = OrionAdapter;
	    break;
	  case Bt485:
	  case Bt485A:
	  case ATT20C505:
	    adapterType = Orion12Adapter;
	    break;
	  default:
	    break;
	}

	return adapterType;
}

/* TODO: clean up error handling, i.e. return something meaningful rather
 * than returning [super free]; */
- (QVAdapterType) determinePCICardType
{
    unsigned int physicalAddress;
    int     numRanges;
    IOPCIConfigSpace configSpace;
    unsigned char devNum, funcNum, busNum;
    unsigned long vendorDeviceID;
    unsigned short vendorID, deviceID;
    char    idString[11];
    id      deviceDescription;
    QVAdapterType adapterType;
    IOConfigTable *configTable;
    IORange *oldRange, newRange[3];

    adapterType = UnknownAdapter;
    if (![self isPCIPresent]) {
	IOLog("%s: No PCI bus found.\n",[self name]);
	return UnknownAdapter;
    }
    deviceDescription = [self deviceDescription];
    [deviceDescription getPCIdevice:&devNum function:&funcNum bus:&busNum];
    IOLog("%s: PCI Dev: %d Func: %d Bus: %d\n", [self name], devNum,
	       funcNum, busNum);
	       
    [self getPCIConfigData:&vendorDeviceID atRegister:0x00];
    vendorID = (unsigned short)vendorDeviceID;
    deviceID = (unsigned short)(vendorDeviceID >> 16);
    IOLog_dbg(("%s: vendorID=%04x deviceID=%04x\n",[self name], vendorID,
	  deviceID));

    /* go through "Auto Detect IDs" and make sure we are okay */
    configTable = [[self deviceDescription] configTable];
    sprintf(idString, "%08lx", vendorDeviceID);
    if (strstr([configTable valueForStringKey:"Auto Detect IDs"], idString)
	== NULL) {
	IOLog("%s: VenderDeviceID %08lx not found in instance table.\n",
	      [self name], vendorDeviceID);
	return UnknownAdapter;
    } else {
	/*
	 * add more card types here if there are PCI cards other than 1280P
	 * actually, if there are more than one PCI cards, we should
	 * determine which PCI card here 
	 */
	adapterType = QVision1280P;
    }

    /* need to do set framebuffer address for PCI */
    [self getPCIConfigSpace:&configSpace];
    physicalAddress = configSpace.BaseAddress[0];
    physicalAddress &= 0xfffffff0;	/* mask out lower 4 bits */

    if (physicalAddress) {
	IOLog_dbg(("%s: try to set physical address to 0x%08x\n",
		   [self name], physicalAddress));
	/* PCI does report where the frame buffer address is */
	oldRange = [deviceDescription memoryRangeList];
	numRanges = [deviceDescription numMemoryRanges];
	if (numRanges == 3) {
	    int     ret;
	    int     i;

	    /* replace the address */
	    for (i = 0; i < numRanges; i++) {
		newRange[i] = oldRange[i];
	    }
	    newRange[0].start = physicalAddress;
	    ret = [deviceDescription setMemoryRangeList:newRange num:3];
	    if (ret) {
		/* can't set to new memory range */
		IOLog("%s: Can't set memory range, using default.\n",
		      [self name]);
		for (i = 0; i < numRanges; i++) {
		    newRange[i] = oldRange[i];
		}
		physicalAddress = newRange[0].start;
		ret = [deviceDescription setMemoryRangeList:newRange
		       num:3];
		if (ret) {
		    /* can't set to old range-->major problem! */
		    IOLog("%s: Can't set to default range either!\n",
			  [self name]);
		    return UnknownAdapter;
		}
	    }
	} else {
	    IOLog("%s: Incorrect number of address ranges: %d.\n",
		  [self name], numRanges);
	    return UnknownAdapter;
	}
    } else {
	IOLog_dbg(("%s: PCI doesn't tell us the physical address.\n",
		   [self name]));
	physicalAddress = [deviceDescription memoryRangeList]->start;
	configSpace.BaseAddress[0] = physicalAddress;
	[self setPCIConfigSpace:&configSpace];
    }
    return adapterType;
}


/* helper method to -determineEISACardType */
- (QVAdapterType)adapterTypeFromEISAID:(unsigned int)cardID
{
    QVAdapterType adapterType;

    adapterType = UnknownAdapter;
    IOLog_dbg(("%s: adapterTypeFromEISAID cardID=0x%08x\n",[self name],
	       cardID));
    switch (cardID) {
      case QVISION_EISA_ID:
	adapterType = QVisionAdapter;
	break;
      case ORION_EISA_ID:
	adapterType = OrionAdapter;
	break;
      case ORION12_EISA_ID:
	adapterType = Orion12Adapter;
	break;
      case QVISION_ISA_ID:
      case ORION_ISA_ID:
      case ORION12_ISA_ID:
	IOLog("%s: Sorry, ISA cards are not supported (id=0x%08x).\n",
	      [self name], cardID);
	break;
      default:
	/* We found some other EISA card.  Just ignore it. */
	break;
    }
    return adapterType;
}

/* helper method to -determineEISACardType */
- (QVAdapterType)autoScanEISAForCardType
{
    int     slot;
    QVAdapterType adapterType;
    unsigned int cardID;

    IOLog_dbg(("%s: doing auto-scan on EISA bus.\n",[self name]));

    adapterType = UnknownAdapter;
    for (slot = 1; slot < 16; slot++) {
	if ([self getEISAId:&cardID forSlot:slot]) {
	    adapterType = [self adapterTypeFromEISAID:cardID];
	}
	if (adapterType != UnknownAdapter) {
	    IOLog_dbg(("%s: found card in slot %d.\n",[self name], slot));
	    return adapterType;
	}
    }
    return UnknownAdapter;
}

/* can't use atoi() nor sscanf() */
- (int) getFirstNumber:(char *)s
{
    char   *cptr;
    int     n;

    cptr = s;
    n = -1;
    while (*cptr && ((*cptr < '0') || (*cptr > '9')))
	cptr++;
    while (*cptr && ((*cptr >= '0') && (*cptr <= '9'))) {
	if (n == -1)
	    n = 0;
	n = n * 10 + (*cptr - '0');
	cptr++;
    }
    return n;
}

/* TODO: clean up error handling, i.e. return something meaningful rather
 * than returning [super free]; */
- (QVAdapterType) determineEISACardType
{
    QVAdapterType adapterType;
    int     mySlot;
    id      deviceDescription;
    const char *slotValue;
    IOConfigTable *configTable;
    unsigned int cardID;

    adapterType = UnknownAdapter;
    if (![self isEISAPresent]) {
	IOLog("%s: Not an EISA system.\n",[self name]);
	return UnknownAdapter;
    }
    deviceDescription = [self deviceDescription];
    configTable = [[self deviceDescription] configTable];

    /* see what slot we're supposed be in */
    slotValue = [configTable valueForStringKey:"Location"];
    if (strstr(slotValue, "Slot")) {
	mySlot = [self getFirstNumber:(char *)slotValue];
	IOLog_dbg(("%s: we should be in slot %d\n",[self name], mySlot));
    } else {
	/* instance table doesn't tell us what slot we're in */
	return [self autoScanEISAForCardType];
    }

    /* TODO: need to check for auto detect id's */
    if (mySlot > 0) {
	if ([self getEISAId:&cardID forSlot:mySlot]) {
	    adapterType = [self adapterTypeFromEISAID:cardID];
	} else {
	    /* can't find card in the specified slot, do auto-scan */
	    adapterType = [self autoScanEISAForCardType];
	}
    } else {
	/* slot not specified, do auto-scan */
	adapterType = [self autoScanEISAForCardType];
    }
    return adapterType;
}

- (void)reportConfiguration
{
    const char *adapterString, *dacString;
    unsigned int biosCapability, memoryPresent;
    const char *capString, *memString;
   
    switch (adapter) {
      case QVisionAdapter:
	adapterString = "QVision";
	break;
      case OrionAdapter:
	adapterString = "Orion";
	break;
      case Orion12Adapter:
	adapterString = "Orion12";
	break;
      case QVision1280P:
	adapterString = "QVision1280P";
	break;
      default:
	adapterString = "Unknown";
	break;
    }

    switch (dac) {
      case Bt484:
	dacString = "Brooktree 484";
	break;
      case Bt485:
	dacString = "Brooktree 485";
	break;
      case Bt485A:
	dacString = "Brooktree 485A";
	break;
      case ATT20C505:
	dacString = "AT&T 20C505";
	break;
      default:
	dacString = "Unknown";
	break;
    }

    /* Unlock */
    outb(VGA_GRFX_INDEX, 0x0F);
    outb(VGA_GRFX_DATA, 0x05);
    
    outb(VGA_GRFX_INDEX, 0x56);
    biosCapability = inb(VGA_GRFX_DATA);
    outb(VGA_GRFX_INDEX, 0x57);
    biosCapability |= (inb(VGA_GRFX_DATA) << 8);
    
    /* TITAN == Bt484, TITAN-1280 == Bt484, GIANT == Bt485 (64-bit) */
    
    switch (biosCapability) {
      case 0x2F3A:
      case 0x2FBA:
	capString = "QV64P10/GIANT";
	break;
      case 0x2F36:
      case 0x2FB6:
	capString = "QV64P10/TITAN-1280";
	break;
      case 0x2F32:
      case 0x2FB2:
	capString = "QV64P10/TITAN";
	break;
      case 0x0936:
	capString = "QV35P04/TITAN-1280";
	break;
      case 0x0932:
	capString = "QV35P04/TITAN";
	break;
      default:
	capString = "Unknown";
	break;
    }
    
    /* Now find amount of installed memory */
    outb(VGA_GRFX_INDEX, 0x54);
    memoryPresent = inb(VGA_GRFX_DATA);
    installedVRAMBytes = (memoryPresent * 1024 * 1024)/4;
    
    switch (memoryPresent) {
      case 0x02:
	memString = "0.5MB";
	break;
      case 0x04:
	memString = "1MB";
	break;
      case 0x08:
	memString = "2MB";
	break;
      case 0x10:
	memString = "4MB";
	break;
      default:
	memString = "Unknown";
	break;
    }

    IOLog("%s: %s: %s/%s/%s\n", [self name], adapterString, capString, dacString, memString);
    
    /* Relock */
    outb(VGA_GRFX_INDEX, 0x0F);
    outb(VGA_GRFX_DATA, 0x0);
}


- determineConfiguration
{
    adapter = UnknownAdapter;

    [self determineConfigBusType];
    [self determineDACType];
    
    switch (busType) {
      case BusISAorVL:
	adapter = [self determineVLCardType];
	break;
      case BusPCI:
	adapter = [self determinePCICardType];
	break;
      case BusEISA:
	adapter = [self determineEISACardType];
	break;
    }
    [self reportConfiguration];
    if (adapter == UnknownAdapter || dac == UnknownDAC)
	return nil;
    return self;
}

/*
 * Initialize all fields in the QVision mode table. 
 */
- (void)initModeList
{
    int     i, n;
    QVisionMode *qvMode;
    unsigned int memoryNeeded;
    
    for (i = 0; i < QVisionModeTableCount; i++) {
        QVisionModeTable[i].flags = IO_DISPLAY_HAS_TRANSFER_TABLE;
	QVisionModeTable[i].modeUnavailableFlag = IO_DISPLAY_MODE_VALID;
    }
    
    for (i = 0; i < QVisionModeTableCount; i++) {
	memoryNeeded = QVisionModeTable[i].rowBytes * 
		QVisionModeTable[i].height;
		
        if (installedVRAMBytes < memoryNeeded)
	    QVisionModeTable[i].modeUnavailableFlag |= 
	    	IO_DISPLAY_MODE_NEEDS_MORE_MEMORY;
    }
    
    for (i = 0; i < QVisionModeTableCount; i++) {
	memoryNeeded = QVisionModeTable[i].rowBytes * 
		QVisionModeTable[i].height;
	for (n = 1024; n < memoryNeeded; n *= 2)
	    ;
	QVisionModeTable[i].memorySize = n;
    }
    
    for (i = 0; i < QVisionModeTableCount; i++) {
	qvMode = QVisionModeTable[i].parameters;
	if (qvMode->adapter > adapter)
	    QVisionModeTable[i].modeUnavailableFlag |= 
	    	IO_DISPLAY_MODE_WRONG_VERSION;
    }

    /*
     * Add code here that fills in other fields as well.
     */
}

- selectMode
{
    int     mode;

    mode = [self selectMode:QVisionModeTable count:QVisionModeTableCount
	    valid:0];

    if (mode < 0) {
	mode = DEFAULT_QVISION_MODE;
    }
    *[self displayInfo] = QVisionModeTable[mode];
    
    return self;
}

- initializeMode
{
    unsigned int i;
    const QVisionMode *mode;
    const IODisplayInfo *displayInfo;

    displayInfo = [self displayInfo];
    mode = displayInfo->parameters;

    /* Turn off video while setting all of the registers. */
    inb(VGA_INPUT_STATUS_1);
    outb(VGA_ATTR_INDEX, 0x00);

    /* Set the sequencer registers. */
    for (i = 0; i < VGA_SEQ_COUNT; i++) {
	outb(VGA_SEQ_INDEX, i);
	outb(VGA_SEQ_DATA, mode->vgaData.seqx[i]);
    }
    outb(VGA_SEQ_INDEX, 0x00);
    outb(VGA_SEQ_DATA, 0x03);	/* Restart the sequencer. */

    /* Unlock extended graphics registers. */
    outw(VGA_GRFX_INDEX, 0x050f);

    /* Unlock more extended registers. */
    outb(VGA_GRFX_INDEX, 0x10);
    outb(VGA_GRFX_DATA, 0x08);

    /* Set Advanced VGA mode (set bit 0 of Ctrl Reg 0). */
    outb(VGA_GRFX_INDEX, 0x40);
    outb(VGA_GRFX_DATA, 0x01);

    /* Fix sequencer pixel mask for 8 bits. */
    outb(VGA_SEQ_INDEX, SEQ_PIXEL_WR_MSK);
    outb(VGA_SEQ_DATA, 0xff);

    outb(CTRL_REG_1, mode->ctrlReg1);
    if (mode->adapter >= OrionAdapter) {
	/* Set access level & enable high address map. */
	outb(QVGA_CTL_2, 0x14);
	/* Select 2 Meg mode. */
	outb(QVGA_CTL_3, 0x05);
    }
    /* Set miscellaneous output register. */
    outb(VGA_MISC_OUTPUT, mode->vgaData.miscOutput);

    [self programDAC];

    /* Load CRTC registers. */
    outb(VGA_CRTC_INDEX, 0x11);	/* Unlock CRTC regs 0-7. */
    outb(VGA_CRTC_DATA, 0x00);
    for (i = 0; i < VGA_CRTC_COUNT; i++) {
	outb(VGA_CRTC_INDEX, i);
	outb(VGA_CRTC_DATA, mode->vgaData.crtc[i]);
    }

    /* Load overflow registers. */
    outb(VGA_GRFX_INDEX, 0x42);
    outb(VGA_GRFX_DATA, mode->overflow1);
    outb(VGA_GRFX_INDEX, 0x51);
    outb(VGA_GRFX_DATA, mode->overflow2);

    /* Load attribute registers. */

    inb(VGA_INPUT_STATUS_1);	/* Reset latch. */
    for (i = 0; i < VGA_ATTR_COUNT; i++) {
	outb(VGA_ATTR_INDEX, i);
	outb(VGA_ATTR_DATA, mode->vgaData.attr[i]);
    }

    /* Load graphics registers. */
    for (i = 0; i < VGA_GRFX_COUNT; i++) {
	outb(VGA_GRFX_INDEX, i);
	outb(VGA_GRFX_DATA, mode->vgaData.grfx[i]);
    }
    
    [self setGammaTable];

    /* Re-enable video display. */
    inb(VGA_INPUT_STATUS_1);
    outb(VGA_ATTR_INDEX, 0x20);

    return self;
}

- enableLinearFrameBuffer
{
    const IODisplayInfo *displayInfo;
    unsigned char tmp;

    /*
     * Override the high address map disable, thus allowing access to the
     * high address map of the current board, even when the board is
     * disabled. 
     */

    outb(VGA_GRFX_INDEX, HI_ADDR_MAP + 1);
    tmp = inb(VGA_GRFX_DATA);
    outb(VGA_GRFX_DATA, tmp | 0x80);

    /* Map VRAM.  Tell the adapter where to decode the framebuffer. */

    /* Set low 8 bits */
    outb(VGA_GRFX_INDEX, HI_ADDR_MAP);
    outb(VGA_GRFX_DATA, (videoRamAddress >> 20) & 0xFF);

    /* Set upper 4 bits */
    outb(VGA_GRFX_INDEX, HI_ADDR_MAP + 1);
    outb(VGA_GRFX_DATA, (videoRamAddress >> 28) & 0x0F);

    /* Leave them with a nice clear screen. */

    displayInfo = [self displayInfo];
    memset(displayInfo->frameBuffer, 0,
	   displayInfo->rowBytes * displayInfo->height);

    return self;
}

- resetVGA
{
    const IODisplayInfo *displayInfo;
    const QVisionMode *mode;

    displayInfo = [self displayInfo];
    mode = displayInfo->parameters;

    /* Clear the QVision extended mode bit. This is bit 0 of CTRL_REG_1. */

    outb(CTRL_REG_1, inb(CTRL_REG_1) & 0xFE);

    if (mode != 0 && mode->adapter >= OrionAdapter) {
	/* Select 1 meg mode. */
	outb(QVGA_CTL_3, 0x00);
	/* Reset access level & disable high address map. */
	outb(QVGA_CTL_2, 0x00);
    }
    /* Clear the extended 256 color bit. This is bit 0 of 3CF.40. */
    outb(VGA_GRFX_INDEX, 0x40);
    outb(VGA_GRFX_DATA, (inb(VGA_GRFX_DATA) & 0xFE));

    /* Clear the page registers - 3CF.45 and 3CF.46. */
    outb(VGA_GRFX_INDEX, PAGE_REG_0);
    outb(VGA_GRFX_DATA, 0x00);
    outb(VGA_GRFX_INDEX, PAGE_REG_1);
    outb(VGA_GRFX_DATA, 0x00);

    [self resetDAC];

    /* Clear the overflow registers. */
    outb(VGA_GRFX_INDEX, 0x42);
    outb(VGA_GRFX_DATA, 0x00);
    outb(VGA_GRFX_INDEX, 0x51);
    outb(VGA_GRFX_DATA, 0x00);

    VGASetMode(0x03);

    return self;
}
@end
